Crate genawaiter

Source
Expand description

This crate implements generators for Rust. Generators are a feature common across many programming language. They let you yield a sequence of values from a function. A few common use cases are:

  • Easily building iterators.
  • Avoiding allocating a list for a function which returns multiple values.

Rust has this feature too, but it is currently unstable (and thus nightly-only). But with this crate, you can use them on stable Rust!

§Features

This crate has these features:

  • futures03 (disabled by default) – Implements Stream for all generator types. Adds a dependency on futures-core.
  • proc_macro (enabled by default) – Adds support for macros, and adds various compile-time dependencies.

§Choose your guarantees

This crate supplies three concrete implementations of generators:

  1. genawaiter::stack – Allocation-free. You should prefer this when possible.

  2. genawaiter::rc – This allocates.

  3. genawaiter::sync – This allocates, and can be shared between threads.

Here are the differences in table form:

stack::Genrc::Gensync::Gen
Allocations per generator022
Generator can be moved after creatednoyesyes
Thread-safenonoyes

§Creating a generator

Once you’ve chosen how and whether to allocate (see previous section), you can create a generator using a macro from the gen family:

let count_to_ten = gen!({
    for n in 0..10 {
        yield_!(n);
    }
});

To re-use logic between multiple generators, you can use a macro from the producer family, and then pass the producer to Gen::new.

let count_producer = producer!({
    for n in 0..10 {
        yield_!(n);
    }
});

let count_to_ten = Gen::new(count_producer);

If neither of these offers enough control for you, you can always skip the macros and use the low-level API directly:

let count_to_ten = Gen::new(|co| async move {
    for n in 0..10 {
        co.yield_(n).await;
    }
});

§A tale of three types

A generator can control the flow of up to three types of data:

  • Yield – Each time a generator suspends execution, it can produce a value.
  • Resume – Each time a generator is resumed, a value can be passed in.
  • Completion – When a generator completes, it can produce one final value.

§Yield

Values can be yielded from the generator by calling yield_, and immediately awaiting the future it returns. You can get these values out of the generator in either of two ways:

  • Call resume() or resume_with(). The values will be returned in a GeneratorState::Yielded.

    let mut generator = gen!({
        yield_!(10);
    });
    let ten = generator.resume();
    assert_eq!(ten, GeneratorState::Yielded(10));
  • Treat it as an iterator. For this to work, both the resume and completion types must be () .

    let generator = gen!({
        yield_!(10);
    });
    let xs: Vec<_> = generator.into_iter().collect();
    assert_eq!(xs, [10]);

§Resume

You can also send values back into the generator, by using resume_with. The generator receives them from the future returned by yield_.

let mut printer = gen!({
    loop {
        let string = yield_!(());
        println!("{}", string);
    }
});
printer.resume_with("hello");
printer.resume_with("world");

§Completion

A generator can produce one final value upon completion, by returning it from the function. The consumer will receive this value as a GeneratorState::Complete.

let mut generator = gen!({
    yield_!(10);
    "done"
});
assert_eq!(generator.resume(), GeneratorState::Yielded(10));
assert_eq!(generator.resume(), GeneratorState::Complete("done"));

§Async generators

If you await other futures inside the generator, it becomes an async generator. It does not makes sense to treat an async generator as an Iterable, since you cannot await an Iterable. Instead, you can treat it as a Stream. This requires opting in to the dependency on futures with the futures03 feature.

[dependencies]
genawaiter = { version = "...", features = ["futures03"] }
async fn async_one() -> i32 { 1 }
async fn async_two() -> i32 { 2 }

let gen = gen!({
    let one = async_one().await;
    yield_!(one);
    let two = async_two().await;
    yield_!(two);
});
let stream = block_on_stream(gen);
let items: Vec<_> = stream.collect();
assert_eq!(items, [1, 2]);

Async generators also provide a async_resume method for lower-level control. (This works even without the futures03 feature.)

match gen.async_resume().await {
    GeneratorState::Yielded(_) => {}
    GeneratorState::Complete(_) => {}
}

§Backported stdlib types

This crate supplies Generator and GeneratorState. They are copy/pasted from the stdlib (with stability attributes removed) so they can be used on stable Rust. If/when real generators are stabilized, hopefully they would be drop-in replacements. Javascript developers might recognize this as a polyfill.

There is also a Coroutine trait, which does not come from the stdlib. A Coroutine is a generalization of a Generator. A Generator constrains the resume argument type to (), but in a Coroutine it can be anything.

Modules§

  • This module implements a generator which stores its state on the heap.
  • This module implements a generator which doesn’t allocate.
  • This module implements a generator which can be shared between threads.

Macros§

Enums§

Traits§

  • A trait implemented for coroutines.
  • A trait implemented for generator types.